/*
 * GClubHistoryRecord.java
 * Created on 2 févr. 2008
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2008, Jens and Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.dao.club;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Locale;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.hockeo.vhbase.data.dao.IGEntity;
import com.hockeo.vhbase.data.dao.contest.GContest;
import com.hockeo.vhbase.data.dao.season.GSeason;
import com.hockeo.vhbase.data.dao.standings.GECRStandings;
import com.hockeo.vhbase.data.dao.standings.GENRStandings;
import com.hockeo.vhbase.data.helper.GClubMultiRoundContestStatus;
import com.hockeo.vhbase.data.helper.GContestRoundType;
import com.hockeo.vhbase.data.helper.GMatchResult;
import com.hockeo.vhbase.data.helper.GMedalType;
import com.hockeo.vhbase.data.helper.GTranslationHelper;

/**
 * Base class for all classes that record a club's result in a contest for a given season.
 * There must be a subclass for each type of contest for which records should be recorded.
 *
 * @version $Id$
 * @author jjanke
 */
@Entity
@Table( name = "h_club_history" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "contest_type", discriminatorType = DiscriminatorType.STRING )
public abstract class GClubHistoryRecord implements IGEntity, Comparable<GClubHistoryRecord>
{
  @Id
  @Column( name = "id" )
  @GeneratedValue( strategy = GenerationType.AUTO )
  private int               d_nId;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "season_id" )
  private GSeason           d_season;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "club_id" )
  private GClub             d_club;

  @ManyToOne( fetch = FetchType.LAZY )
  @JoinColumn( name = "contest_id" )
  private GContest          d_contest;

  @Column( name = "rank" )
  private int               d_nRank               = 0;

  @Enumerated( EnumType.STRING )
  @Column( name = "medal" )
  private GMedalType        d_medal               = GMedalType.NONE;

  @Enumerated( EnumType.STRING )
  @Column( name = "reached_round" )
  private GContestRoundType d_reachedRound        = GContestRoundType.NOT_DEFINED;

  @Column( name = "matches" )
  private int               d_nNumMatches         = 0;

  @Column( name = "wins" )
  private int               d_nNumWins            = 0;

  @Column( name = "wins_ot" )
  private int               d_nNumWinsOT          = 0;

  @Column( name = "losses_ot" )
  private int               d_nNumLossesOT        = 0;

  @Column( name = "losses" )
  private int               d_nNumLosses          = 0;

  @Column( name = "points" )
  private int               d_nNumPoints          = 0;

  @Column( name = "won_ko_series" )
  private int               d_nNumWonKOSeries     = 0;

  @Column( name = "lost_ko_series" )
  private int               d_nNumLostKOSeries    = 0;

  @Column( name = "points_pct" )
  private BigDecimal        d_decPointsPercentage = new BigDecimal( "1.000" );

  @Column( name = "points_avg" )
  private BigDecimal        d_decPointsAverage    = new BigDecimal( "0.000" );

  @Column( name = "points_ecr" )
  private BigDecimal        d_decPointsECR        = new BigDecimal( "0.00" );

  /** Default parameterless constructor. */
  protected GClubHistoryRecord()
  {}

  /**
   * Creates a new club/season/contest history record.
   */
  protected GClubHistoryRecord( GSeason season, GClub club, GContest contest )
  {
    d_season = season;
    d_club = club;
    d_contest = contest;
  }

  /**
   * Resets all fields (but club, season and contest) to their default values.
   */
  public void reset()
  {
    d_nRank = 0;
    d_reachedRound = GContestRoundType.NOT_DEFINED;
    d_nNumMatches = 0;
    d_nNumWins = 0;
    d_nNumWinsOT = 0;
    d_nNumLossesOT = 0;
    d_nNumLosses = 0;
    d_nNumPoints = 0;
    d_nNumWonKOSeries = 0;
    d_nNumLostKOSeries = 0;
    d_decPointsPercentage = new BigDecimal( "1.000" );
    d_decPointsAverage = new BigDecimal( "0.000" );
    d_decPointsECR = new BigDecimal( "0.00" );
  }

  /**
   * Returns the current status of the club this object belongs to if the contest whose
   * history is recorded, is a multi-round contest. If it is not a multi-round contest,
   * the default constant {@link GClubMultiRoundContestStatus#REACHED_LAST_ROUND} should
   * be returned.
   */
  public abstract GClubMultiRoundContestStatus getMultiRoundContestStatus();

  /**
   * Sets the ECR points recorded by this history object to the passed in ECR standings
   * object.
   *
   * @param ecr the GECRStandings object to be updated
   */
  public abstract void updateECRStandings( GECRStandings ecr );

  /**
   * Sets the ECR points recorded by this history object to the passed in ENR standings
   * object. Whether some points are set or not depends on the actual subclass
   * implementing this method.
   *
   * @param enr the GECRStandings object to be updated
   */
  public abstract void updateENRStandings( GENRStandings enr );

  public GSeason getSeason()
  {
    return d_season;
  }

  public GClub getClub()
  {
    return d_club;
  }

  public GContest getContest()
  {
    return d_contest;
  }

  public int getRank()
  {
    return d_nRank;
  }

  public GMedalType getMedal()
  {
    return d_medal;
  }

  public GContestRoundType getReachedRound()
  {
    return d_reachedRound;
  }

  /**
   * Returns the localised name of the reached round. It is first checked whether there is
   * a translation available for the specific contest. If not, the general round type
   * translation is returned.
   *
   * @param locale the locale to use for translation
   * @return the translated reached round type name
   */
  public String getReachedRound( Locale locale )
  {
    // First try with contest id
    //
    String strKey = "roundType_" + getReachedRound().name() + "_" + getContest().getId();
    String str = GTranslationHelper.get().translate( strKey, locale );

    // If it does not exist, try with round type
    //
    if ( str.equals( strKey ) )
      str = GTranslationHelper.get().translate( "roundType_" + getReachedRound().name(), locale );

    return str;
  }

  public int getNumMatches()
  {
    return d_nNumMatches;
  }

  public int getNumWins()
  {
    return d_nNumWins;
  }

  public int getNumWinsOT()
  {
    return d_nNumWinsOT;
  }

  public int getNumLossesOT()
  {
    return d_nNumLossesOT;
  }

  public int getNumLosses()
  {
    return d_nNumLosses;
  }

  public int getNumPoints()
  {
    return d_nNumPoints;
  }

  public BigDecimal getPointsPerMatchAvg()
  {
    return d_decPointsAverage;
  }

  public BigDecimal getPointsPercentage()
  {
    return d_decPointsPercentage;
  }

  public BigDecimal getPointsECR()
  {
    return d_decPointsECR;
  }

  /**
   * Sets the new rank and medal (only for ranks 1 to 3).
   *
   * @param nRank the new rank to be set
   */
  public void setRank( int nRank )
  {
    d_nRank = nRank;

    switch ( d_nRank )
    {
      case 1:
        d_medal = GMedalType.GOLD;
        break;

      case 2:
        d_medal = GMedalType.SILVER;
        break;

      case 3:
        d_medal = GMedalType.BRONZE;
        break;

      default:
        d_medal = GMedalType.NONE;
    }
  }

  protected void setReachedRound( GContestRoundType roundType )
  {
    d_reachedRound = roundType;
  }

  protected void setNumMatches( int nNumMatches )
  {
    d_nNumMatches = nNumMatches;
  }

  protected void setNumWins( int nNumWins )
  {
    d_nNumWins = nNumWins;
  }

  protected void setNumWinsOT( int nNumWinsOT )
  {
    d_nNumWinsOT = nNumWinsOT;
  }

  protected void setNumLossesOT( int nNumLossesOT )
  {
    d_nNumLossesOT = nNumLossesOT;
  }

  protected void setNumLosses( int nNumLosses )
  {
    d_nNumLosses = nNumLosses;
  }

  protected void setNumPoints( int nNumPoints )
  {
    d_nNumPoints = nNumPoints;
  }

  public void setNumWonKOSeries( int numWonKOSeries )
  {
    d_nNumWonKOSeries = numWonKOSeries;
  }

  public int getNumWonKOSeries()
  {
    return d_nNumWonKOSeries;
  }

  public void setNumLostKOSeries( int numLostKOSeries )
  {
    d_nNumLostKOSeries = numLostKOSeries;
  }

  public int getNumLostKOSeries()
  {
    return d_nNumLostKOSeries;
  }

  protected void setPointsECR( BigDecimal decPointsECR )
  {
    d_decPointsECR = decPointsECR;
  }

  protected void setPointsPerMatchAvg( BigDecimal decPointsAvg )
  {
    d_decPointsAverage = decPointsAvg;
  }

  protected void setPointsPercentage( BigDecimal decPointsPct )
  {
    d_decPointsPercentage = decPointsPct;
  }

  /**
   * Updates the points per match average and the points percentage so that it matches the
   * currently set number of matches and points.
   */
  protected void updatePointsAvgPct()
  {
    if ( d_nNumMatches == 0 )
      return;

    d_decPointsAverage = new BigDecimal( d_nNumPoints / (double) d_nNumMatches ).setScale( 3, RoundingMode.HALF_UP );
    d_decPointsPercentage = new BigDecimal( d_nNumPoints
        / (double) ( d_nNumMatches * GMatchResult.VICTORY.getNumPoints() ) ).setScale( 3, RoundingMode.HALF_UP );
  }

  public int getId()
  {
    return d_nId;
  }

  /**
   * Imposes an ordering on club/history records. This ordering is the official ordering
   * which is used to classify clubs in multi-round cup competitions.
   */
  public int compareTo( GClubHistoryRecord other )
  {
    int nTmp;

    // 1st criteria: by round type order ASC
    //
    nTmp = this.getReachedRound().compareTo( other.getReachedRound() );
    if ( nTmp != 0 )
      return nTmp;

    // 2nd criteria: amount of ECR points DESC
    //
    nTmp = other.getPointsECR().compareTo( this.getPointsECR() );
    if ( nTmp != 0 )
      return nTmp;

    // 3rd criteria: amount of victories DESC
    //
    nTmp = other.getNumWins() - this.getNumWins();
    if ( nTmp != 0 )
      return nTmp;

    // 4th criteria: amount of overtime victories DESC
    //
    return other.getNumWinsOT() - this.getNumWinsOT();
  }

  public boolean instanceOf( Class<? extends IGEntity> classOther )
  {
    return classOther.isAssignableFrom( getGEntityClass() );
  }

  /*
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals( Object obj )
  {
    try
    {
      return d_nId == ( (GClubHistoryRecord) obj ).getId();
    }
    catch ( Exception ex ) // especially ClassCastException(s) may happen
    {
      return false;
    }
  }

  /*
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode()
  {
    return d_nId;
  }

  /*
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString()
  {
    return "Club history record " + d_nId + " for " + d_club.getName() + " (contest: " + d_contest.getId()
        + " and season " + d_season.getName() + ")";
  }
}
